/*
 * This file is part of J2ME VNC.
 *
 * Copyright (c) 2003-2005 Michael Lloyd Lee
 *
 * J2ME VNC is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * J2ME VNC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with J2ME VNC; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package tk.wetnet.j2me.vnc;

import java.util.Vector;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;


import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.ChoiceGroup;
import javax.microedition.lcdui.Gauge;
import javax.microedition.lcdui.TextField;
import javax.microedition.lcdui.TextBox;
import javax.microedition.lcdui.Choice;

import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordEnumeration;

import javax.microedition.io.StreamConnection;
import javax.microedition.io.Connector;
import javax.microedition.io.ConnectionNotFoundException;
import javax.microedition.io.InputConnection;
import javax.microedition.io.HttpConnection;


import tk.wetnet.vnc.RFBProto;
import tk.wetnet.vnc.DrawToable;

/** The main MIDlet
 * This has a form for the connection info (server IP/port etc)
 * Then it creates the socket connection between the device and the server,
 * passes the IO streams to a newly created RFBProto, and creates a VNC Canvas,
 * and passes the RFB to the canvas.
 */

public class VNC extends MIDlet implements CommandListener, Runnable {
    /** The record store holding the "Quick Links" */
    private RecordStore rs = null;
    /** The record store holding the options, such as is NCM enabled */
    private RecordStore options = null;
    /** A vector of Host Commands (the internal representation of Quick Links
     * @see HostCommand 
     */
    private Vector hostCmds = new Vector();

    /** The midlets Display object */
    protected Display me;
    /** The VNC Canvas */
    protected VNCCanvas canvas;
    /** Our rfb proto */
    protected RFBProto rfb;
    /** The current Stream Connection */
    private StreamConnection con;
    /** A tempory thread used to set up the initial connection*/
    private Thread run;


    //#ifdef DEBUG
    //# Command log = new Command( "Log", Command.OK, 0 );
    //# private Command uploadLog = new Command( "Upload" , Command.OK, 0 );
    //# private Command backLog = new Command( "Back",  Command.OK, 0 );
    //# 
    //# private Displayable backDisplayable = null;
    //#endif


    private Form connectionForm =  new Form("VNC");
    private Form connectingForm = new Form("VNC - #connecting#"); 
    private TextField url = new TextField( "#host#", "", 25, TextField.ANY );
    private TextField password = new TextField( "#password#", "", 
						14, TextField.PASSWORD );
    private Command connect = new Command( "#connect#", Command.OK, 20 );

    private Command add = new Command( "#add#", Command.ITEM, 9 );
    private Command manage = new Command( "#manage#", Command.ITEM, 11 );

    private Command delete = new Command( "#delete#", Command.OK, 0 );
    private Command back = new Command( "#back#", Command.BACK, 1 );
    private Command exit = new Command( "#exit#", Command.EXIT, 1 );

    private ChoiceGroup hosts = null;
    private String host = "";
    private int port = 5900;
  
    private Gauge connectionDisplay = new Gauge( "#connecting#", false, 5, 0 );

    /** Tick boxes for each of the options (like NCM, shared desktop, use HTTP)
     */
    private ChoiceGroup shared = new ChoiceGroup( "", Choice.MULTIPLE );

    /** The connection stage, between 0 & 4*/
    private int conD = 0;

    /** Increase the conD by one, and updates the connection display gauge
     * @see connectionDisplay
     * @see conD
     */
    protected void incConnectionStatus() {
	conD++;
	connectionDisplay.setValue( conD );
    }

    /** 
     * Then it creates the socket connection between the device and the server,
     * passes the IO streams to a newly created RFBProto, and creates a 
     * VNC Canvas, and passes the RFB to the canvas, occasionally calling
     * inc Connection Status
     * @see me
     * @see incConnectionStatus
     */
    public void run() {
	if( url.getString() == null || 
	    ( url.getString() != null 
	      && url.getString().length() < 1 ) ) {
	    Alert a = new Alert( "#no_host#", "#host_box_blank#", 
				 null, AlertType.ERROR );
	    a.setTimeout( a.FOREVER );
	    me.setCurrent( a , connectionForm );
	    return;
	}

	/** 
	 * @random_idea Add an animated clock while it connects
	 */
	Thread.currentThread().yield();
	incConnectionStatus();
	host = url.getString();
	if( host.indexOf( ":" ) >= 0 ) {
	    try {
		if( host.charAt( host.indexOf( ":" )+1 ) != ':' ) {
		    port = Integer.parseInt( host.substring
					     ( host.indexOf( ":" )+1, 
					       host.length() ) );

		    //#ifndef JHTTP_TUNNEL
		    //# if( port < 5900 ) port += 5900;
		    //#endif
		} else {
		    port = Integer.parseInt( host.substring
					     ( host.indexOf( ":" )+2, 
					       host.length() ) );
		}
	    } catch( NumberFormatException nfe ) {
		Alert a = new Alert( "#problem_connecting#", 
				     "#format_is_wrong#"
				     + host.substring( host.indexOf( ":" )+1, 
						       host.length() ) 
				     + "#is_not_a_number#",
				     null, AlertType.ERROR );
		a.setTimeout( a.FOREVER );
		me.setCurrent( a , connectionForm );
		return;
	    }
	    host = host.substring( 0, host.indexOf( ":" ));
	}

	//#ifdef DEBUG
	//# log( "Connecting" );
	//#endif

	try {
		con = (StreamConnection)Connector.open( "socket://" + host + ":" + port, Connector.READ_WRITE );
	    incConnectionStatus();
	} catch( IllegalArgumentException uae ) {
	    Alert a = new Alert( "#problem_connecting#", 
				 "#format_is_wrong"
				 + "#please_double_check#" 
				 + "\n" + uae.toString(),  // todo debug var?
				 null, AlertType.ERROR );
	    a.setTimeout( a.FOREVER );
	    me.setCurrent( a , connectionForm );
	    return;
	} catch( ConnectionNotFoundException cnfe ) {
	    Alert a = new Alert( "#problem_connecting#", 
				 "#unable_to_connect#"
				 + (cnfe.getMessage() == null? 
				    "Is the host correct?" :
				    cnfe.getMessage() ) 
				 + "?",
				 null, AlertType.ERROR );
	    a.setTimeout( a.FOREVER );
	    me.setCurrent( a , connectionForm );
	    return; 
	} catch( IOException ioe ) {
	    Alert a = new Alert( "#problem_connecting#", 
				 "#unknown_problem#" + ioe.toString(), 
				 null, AlertType.ERROR );
	    a.setTimeout( a.FOREVER );
	    me.setCurrent( a , connectionForm );
	    return;
	} catch( Throwable t ) {
	    Alert a = new Alert( "#problem_connecting#", 
				 "#unknown_problem#" + t.toString(), 
				 null, AlertType.ERROR );
	    a.setTimeout( a.FOREVER );
	    me.setCurrent( a , connectionForm );
	    return;
	}
    	
	byte[] tmp = password.getString().getBytes();
	byte[] b = new byte[ (tmp.length+1>8?tmp.length+1:8)];
	System.arraycopy( tmp, 0, b, 0, tmp.length );
	//#ifdef DEBUG
	//# log( "Creating VNC Canvas" + b.length + " " + tmp.length);
	//#endif
	canvas = new VNCCanvas( this );
	incConnectionStatus();
	//#ifdef DEBUG
	//# log( "Canvas Created" );
	//#endif
	
	try {	   
	    //#ifdef DEBUG
	    //# log( "Creating RFBProto" );
	    //#endif
	    rfb = new RFBProto( ( ( InputConnection ) con)
				.openDataInputStream(), 
				con.openOutputStream(), 
				b , canvas, shared.isSelected( 0 ),
				shared.isSelected( 1 ) );
	    //#ifdef DEBUG
	    //# log( "RFBProto Created" );
	    //#endif
	} catch( IOException ioe ) {
	    Alert a = new Alert( "#problem_connecting#", 
				 "#unknown_problem#" + ioe.toString(), 
				 null, AlertType.ERROR );
	    a.setTimeout( a.FOREVER );
	    me.setCurrent( a , connectionForm );
	    return;
	} catch( Throwable t ) {
	    Alert a = new Alert( "#problem_connecting#", 
				 "#unknown_problem#" + t.toString(), 
				 null, AlertType.ERROR );
	    a.setTimeout( a.FOREVER );
	    me.setCurrent( a , connectionForm );
	    return;
	}
	run = new Thread( rfb );
	run.start();
	cleanUp();
	incConnectionStatus();
	try {
	    rs.closeRecordStore();
	} catch( Throwable t ){}	
    }
	
    public void commandAction(Command c, Displayable d) {
	if( c == connect ) {
	    me.setCurrent( connectingForm );
	    (new Thread( this )).start();
	} else if( c == add ) {
	    try {		
		String tmp = url.getString() + "|" + password.getString();
		HostCommand cm = new HostCommand( url.getString(), 
						  Command.ITEM, 10, 
						  rs.addRecord
						  ( tmp.getBytes(), 0, 
						    tmp.length() ) );
		hostCmds.addElement( cm );
		connectionForm.addCommand( cm );
	    } catch( Throwable t ){
		System.err.println( "Save: " + t );
	    }
	} else if( c == manage ) {
	    Form manageForm = new Form( "#manage_hosts#" );
	    hosts = new ChoiceGroup( "#host_#", ChoiceGroup.EXCLUSIVE );
	    try {
		int size = hostCmds.size();;
		//System.out.println( size );
		for(int i = 0; i < size; i++ ) {
		    HostCommand current = (HostCommand)hostCmds.elementAt(i);
		    //System.out.println( i + " " + current.id + " " 
		    //			+ current.getLabel());
		    hosts.append( current.id + " " + current.getLabel(), 
				  null );
		}
	    } catch( Throwable t ) { System.err.println(t); }
	    manageForm.setCommandListener( this );
	    manageForm.append( hosts );
	    manageForm.addCommand( delete );
	    manageForm.addCommand( back );
	    me.setCurrent( manageForm );
	} else if( c == delete ) {
	    String removes = hosts.getString( hosts.getSelectedIndex() );
	    int remove = Integer.parseInt( removes.substring
					   ( 0, removes.indexOf( " " ) ) );
	    //System.out.println( remove );
	    hosts.delete( hosts.getSelectedIndex() );
	    try {
		rs.deleteRecord( remove );
	    } catch( Throwable t ) {
		System.err.println("delete" + t );
	    }	    
	    for( int i = hostCmds.size()-1; i>=0;i--){
		if( ((HostCommand)hostCmds.elementAt( i )).id == remove ) {
		    connectionForm.removeCommand( ((HostCommand)hostCmds
						   .elementAt( i )) );
		    hostCmds.removeElementAt( i );
		}
	    }
	} else if ( c == exit ) {
	   	notifyDestroyed();

	    //#ifdef DEBUG
//#     } else if( c == log ) {
	    //# Form l = new Form( last_line );
	    //# l.append( complete_log );
	    //# l.setCommandListener( this );
	    //# l.addCommand( backLog );
	    //# l.addCommand( uploadLog );
	    //# backDisplayable = d;
	    //# me.setCurrent( l );
	//# } else if( c == backLog ) {
	   //#  me.setCurrent( backDisplayable );
	//# } else if( c == uploadLog ) {

	//#     InputStream is = null;
	    //# OutputStream os = null;
	    //# HttpConnection c1 = null;
	    //# try {
		//# con.close();
		
		//# c1 = (HttpConnection)Connector.
		    //# open( "http://j2mevnc.sourceforge.net/cgi-bin/upload.pl",
			  //# Connector.READ_WRITE, true );
		
		// Set the request method and headers
		//# c1.setRequestMethod( HttpConnection.POST );
		// Getting the output stream may flush the headers
		//# c1.setRequestProperty
		//#     ( "If-Modified-Since",
		//#       "29 Oct 1999 19:43:31 GMT");
	//# 	c1.setRequestProperty
	//# 	    ("User-Agent",
	//# 	     "Upload");
	//# 	c1.setRequestProperty("Content-Language",
				      //"en-gb");

	//# 	os = c1.openOutputStream();
	//# 	os.write(complete_log.getBytes());
	//# 		os.close(); 
        //#        
		// Opening the InputStream will open the connection
		// and read the HTTP headers. They are stored until
//# 		// requested.
//# 		is = c1.openInputStream();

		// Get the ContentType
//# 		String type = c1.getType();
//# 
//# 		String s;

//# 		if ( c1.getResponseCode() != c1.HTTP_OK ) {
//# 		    s = c1.getResponseCode() + " : " 
//# 			+ c1.getResponseMessage() ;
//# 		} else {
//# 		    // Get the length and process the data
//# 		    int len = (int)c1.getLength();
//# 		    if (len > 0) {
//# 			byte[] data = new byte[len];
//# 			int actual = is.read(data);
//# 			s = new String( data );
//# 		    } else {
//# 			int ch;
//#			StringBuffer result = new StringBuffer();
//# 			while ((ch = is.read()) != -1) {
//# 			    result.append( (char)ch );
//# 			}
//#			s = result.toString();
//# 		    }
//# 		}
//# 		Alert a = new Alert( "Uploaded Log", 
//# 				     s, null, null );
//# 		a.setTimeout( Alert.FOREVER );
//# 		me.setCurrent( a, d );
//# 	    } catch( IOException e ) {
//# 		Alert a = new Alert( "Upload Failed", 
//# 				     e.toString(), null, null );
//# 		a.setTimeout( Alert.FOREVER );
//# 		me.setCurrent( a, me.getCurrent() );
//# 	    } finally {
//# 		if (is != null)
//# 		    try { is.close(); } catch( IOException e ) {}
//# 		if (os != null)
//# 		    try { os.close(); } catch( IOException e ) {}
//# 		if (c1 != null)
//# 		    try { c1.close(); } catch( IOException e ) {}	 
//# 	    }
//# 
	    //#endif

	} else if( c == back ) {
	    me.setCurrent( connectionForm );
	} else if( c instanceof HostCommand ) {
	    try {
		me.setCurrent( connectingForm );
		HostCommand cmd = (HostCommand)c;		
		String tmp = new String( rs.getRecord( cmd.id ) );
		url.setString( tmp.substring(0, tmp.indexOf( "|" ) ) );
		password.setString( tmp.substring
				    ( tmp.indexOf( "|" ) + 1, 
				      tmp.length()) );
		(new Thread( this )).start();
	    } catch( Throwable t ){ 
		System.err.println( t ); 
	    }
					 
	}
    }

    /** Our "about" image, this doubles as the standard VNC icon */
    protected Image aboutImage = null;


    /** Constructor
     * Sets up the UI, adds command listeners, reads the hosts 
     * Record Store, and sets up the Quick Links. Reads the options
     * record store, and sets the default options.
     */
    public VNC() {
	me = Display.getDisplay( this );
	
	connectionForm.append( url );
	connectionForm.append( password );
	connectionForm.append( shared );
	connectionForm.addCommand( connect );   
        connectionForm.addCommand( exit );
	connectionForm.addCommand( add );
	connectionForm.addCommand( manage );
	//#ifdef DEBUG
//# 	connectionForm.addCommand( log );
	//#endif

	connectionForm.setCommandListener( this );

	shared.append( "#shareddesktop#", null );
	shared.append( "#ncm#", null );
	//#ifdef JHTTP_TUNNEL
	shared.append( "#usejhttp#", null );
	//#endif
	try {
	    rs = RecordStore.openRecordStore( "hosts", true );
	    RecordEnumeration re = rs.enumerateRecords( null, null, false );
	    while( re.hasNextElement() ) {
		int id = re.nextRecordId();
		String current = new String( rs.getRecord(id) );
		String title = current;
		if( current.indexOf( "|" ) > 0 ) {
		    title = current.substring(0, current.indexOf( "|" ) );
		}
		HostCommand cm = new HostCommand( title, 
						  Command.ITEM, 10, id );
		connectionForm.addCommand( cm );  
		hostCmds.addElement( cm );
	    }


	    options = RecordStore.openRecordStore( "options", true );
	    re = options.enumerateRecords( null, null, false );
	    if( re.hasNextElement() ) {
		rid = re.nextRecordId();
		byte[] opts = options.getRecord(rid) ;
		if( opts != null && opts.length > 0) {
		    // System.out.println( opts.length );
		    shared.setSelectedIndex( 0, (opts[0] & 1)==1 );
		    shared.setSelectedIndex( 1, (opts[0] & 2)==2 );
		}
	    } else {
		rid = -100;;
	    }
	    
	    aboutImage = Image.createImage( "/VNC.png" );
	    //#ifdef DEBUG
//# 	    connectingForm.addCommand( log );
	    //#endif
	    connectingForm.setCommandListener( (CommandListener)this );
	    connectingForm.append( aboutImage );
	    connectingForm.append( connectionDisplay );


	} catch( Throwable t ) {
	    System.err.println( "VNC() : t " + t.toString() );
	    t.printStackTrace();
	}
    }
    /** The record store ID for the options */
    private int rid = 0;


    /**
     * @todo pause app
     */
    protected void pauseApp() {

    }
    
    /** Saves the options, 
     * @todo Rework. The options should be saved on change.
     */
    protected void destroyApp(boolean parm1) throws
	javax.microedition.midlet.MIDletStateChangeException {
	if( canvas != null )
	    canvas.close();
	try {
	    
	    byte[] b = new byte[ 1 ];
	    b[0] = (byte)((shared.isSelected( 0 )?1:0) 
			  +  (shared.isSelected( 1 )?2:0));
	    if( rid != -100 ){
		options.setRecord( rid, b, 0, b.length );
	    } else {
		options.addRecord( b, 0, b.length );
	    }

	    rs.closeRecordStore();
	    options.closeRecordStore();
	    //	    options.deleteRecordStore( "options" );
	} catch( Throwable t ){
	    System.err.println( t.toString() ) ;
	}
    }
    
    /** Called on un-pause or app start.
     * Sets the current display to either the connection form if we are not
     * connected to a server, or the VNC Canvas if we are connected to a server
     * @see MIDlet.startApp
     */
    protected void startApp() 
	throws javax.microedition.midlet.MIDletStateChangeException {
	if( canvas == null )
	    me.setCurrent( connectionForm );
	else 
	    me.setCurrent( canvas ); 
    }

    /** Releases system resources, by null'ing all references, and calling 
     * GC. 
     * None of the following are going to be used again.
     */
    private void cleanUp() {
	connectionForm = null;
	connectingForm = null;

	url = null;
	password = null;
	
	connect = null;
	
	add = null;
	manage = null;
	
	delete = null;
	back = null;
	
	hosts = null;

	System.gc();
    }

    //#ifdef DEBUG
 //#    static String complete_log = "";
 //#    static String last_line = "NOT STARTED TO LOG!";
    
 //#    public static void log(String log) {
//# 	System.out.println( log + "\n" );
//# 	last_line = log;
//# 	complete_log+=log + "\n";
//#     }
    //#endif

}


//  LocalWords:  modeOKCmd keyPressed ASAIK connectionForm usehttp HostCommand
